package sha.pkg1;
import java.util.Random;

public class ECC {
  
    
public void ecc(int a, int b, int p, int x, int y, int privada, int[] Pm){    

//////////// Validamos los puntos a y b ////////////
boolean ab=validaAB(a,b);
if(ab==false){System.out.println("A y B no son puntos válidos");}

else{//elseab

int cantPuntos = obtienePuntos(a,b,p);
//////////// Validamos la clave privada ////////////
if(!validaPrivada(cantPuntos, privada)){
System.out.println("La clave privada no es válida");
}
else
{//elseprivada    
System.out.println("La clave privada ["+privada+"] es válida");    

//////////// Validamos el punto generador ////////////
if(!validaGenerador(x,y,a,p)){
System.out.println("El punto generador no es válido");
}
else{//elsegenerador

System.out.println("\n====LOS DATOS SON VÁLIDOS===\n");    
int Cifrado[][] = new int[2][2];
int Descifrado[] = new int[2];

int publica[] = new int[2];
publica = clavePublica(privada,x,y,a,p);
Cifrado = cifraECC(x,y,a,p,privada,Pm,publica);
Descifrado = descifraECC(x,y,a,p,Cifrado[1],privada);

}//cierre elsegenerados
}//cierre else privada
}//cierre elseab

}



public int[] clavePublica(int n, int x, int y, int a, int p){

    int Xf=0, Yf=0;
    int Q[] = new int[2];

//Si n es igual a uno, el punto es el mismo de entrada    
    if(n==1){Q[0]=x;Q[1]=y;return Q;}
    
//Si no lo es, hay que hacer la suma de los puntos n veces
    else{//else1
    
//contados de sumas realizadas        
    int cuenta=1;

//tendremos que hacer n sumas
    while(cuenta<n){
    
//en el primer caso siempre los puntos serán iguales        
        if(cuenta==1){
        Q = puntosIguales(x,y,a,p);
        Xf = Q[0];
        Yf = Q[1];        
        }
        //desde el segundo caso hay que comprobar si son iguales
        else{ //else2

            //Si son iguales, usamos la funcion de puntos iguales        
            if(Xf==x && Yf==y){
            Q = puntosIguales(Xf,Yf,a,p);
            Xf = Q[0];
            Yf = Q[1];
            }
            //Si son diferentes, usamos la funcion de puntos distintos
            else{//else3
            Q = puntosDistintos(Xf,x,Yf,y,p);
            Xf = Q[0];
            Yf = Q[1];

            }//cierre else3

        }//cierre else2
    
    cuenta++;
    }//cierre while
    
    System.out.println("\nQ resultante: ("+Q[0]+","+Q[1]+")");
    return Q;
    }//cierre else1

}



public int clavePrivada(int limite){
    Random rnd = new Random();
    //Utilizamos la funcion random que devuelve un double, pero casteamos como int
    //Math.random()*límite+1
    //Como no tenemos un límite, lo omitimos
    int n = (int)rnd.nextInt(limite);

    return n;
}


public int obtienePuntos(int a, int b, int p){

    int[][] puntos = new int[p*p][2];
    int i=0;
    
    for(int x=0 ; x<p ; x++){
    
    double z1 =  ( ( (x*x*x)+(a*x) + b ) % p)  ;
    
    //System.out.println("X:"+x+" Z:"+z1);
    
    for(int y=0 ; y<p ; y++){
        
    double z2 =  ( (y*y) % p)  ;
    //System.out.println("Y:"+y+" Z:"+z2);
    
    if(z1==z2){
    //puntos[i][0]=x;
    //puntos[i][1]=y;
    
    //System.out.println("EL PUNTO ("+puntos[i][0]+","+puntos[i][1]+") ES VÁLIDO" );
    System.out.println("EL PUNTO ("+x+","+y+") ES VÁLIDO" );    
    //Al parecer no hace falta guardar los valores, solo mostrarlos y retornar el contador i
    //Pues la clave privada debe ser un número entre 1 e i-1
    i++;
    }
    
    }
    
    
    }
    
    return i;
    
}

//Busca la k a utilizar en el calculo de lamba
public int buscaK(int p, int den){

    int k=0;
    int T=1;
    
    while(T!=0){
        
    T = ((p*k)+1) % den;
    
    k++;
    }    
    
    return k-1;
}

//Calcula la suma de dos puntos iguales. Ej: (2,7)+(2,7)
public int[] puntosIguales(int x, int y, int a, int p){

    int xys[] = new int[2];
    
    int Num = (3*x*x)+a ;
    int Den = 2*y ;    
    
    //Buscamos el K para calcular el modulo
    int K = buscaK(p,Den);
    //Con el K podemos obtener el valor para lambda
    int T = ((p*K)+1) / Den;  
    //Calculamos el valor Lambda
    int lambda = (Num*T) % p;
    if(lambda<0){lambda=p+lambda;}
    System.out.println("Lambda:"+lambda);
    
    //Calculamos Xs e Ys
    xys[0]= ( ( (lambda*lambda) - (2*x) ) % p );
    xys[1]= (lambda*(x-xys[0])-y) % p;
    
    //Si el modulo es negativo total=p-mod
    if(xys[0]<0){xys[0]=p+xys[0];}
    if(xys[1]<0){xys[1]=p+xys[1];}
    
    System.out.println("IGUALES xys[0]:"+xys[0]+" xys[1]:"+xys[1]);
               
    return xys;
}


//Calcula la suma de dos puntos distintos. Ej: (2,7)+(5,2)
public int[] puntosDistintos(int x1, int x2, int y1, int y2, int p){

    int xys[] = new int[2];
    System.out.println("x1:"+x1+" y1:"+y1+" x2:"+x2+" y2:"+y2);
    
    int Num = y2-y1;
    int Den = x2-x1 ;
    
    //Si el denominador es cero, el punto resultante es (0,0)
    //P(x,y) = -P(x,-y)
    if(Den==0){xys[0]=0;xys[1]=0;}
    //Sino, lo calculamos con las fórmulas de ECC para puntos distintos
    else{
    
    //Buscamos el K para calcular el modulo
    int K = buscaK(p,Den);
    //Con el K podemos obtener el valor para lambda
    int T = ((p*K)+1) / Den;  
    int lambda = (Num*T) % p;
    if(lambda<0){lambda=p+lambda;}
    System.out.println("LambdaD:"+lambda);
    
    //Calculamos Xs e Ys
    xys[0] = ((lambda*lambda)-x1-x2)%p;
    xys[1] = (lambda*(x1-xys[0])-y1)%p;
    
    //Si el modulo es negativo total=p-mod 
    if(xys[0]<0){xys[0]=p+xys[0];}
    if(xys[1]<0){xys[1]=p+xys[1];}
    }
    
    System.out.println("DISTINTOS xys[0]:"+xys[0]+" xys[1]:"+xys[1]);
    
    return xys;
}



public int[][] cifraECC(int x, int y, int a, int p,int k, int[] Pm, int[] Q){


    
///////////PARA CONSTRUIR C1////////////    

    int Cx1=0, Cy1=0;
    int C1[] = new int[2];
    
//contados de sumas realizadas        
    int cuentaC1=1;

//tendremos que hacer n sumas
    while(cuentaC1<k){
    
//en el primer caso siempre los puntos serán iguales        
        if(cuentaC1==1){
        C1 = puntosIguales(x,y,a,p);
        Cx1 = C1[0];
        Cy1 = C1[1];        
        }
        //desde el segundo caso hay que comprobar si son iguales
        else{ //else2

            //Si son iguales, usamos la funcion de puntos iguales        
            if(Cx1==x && Cy1==y){
            C1 = puntosIguales(Cx1,Cy1,a,p);
            Cx1 = C1[0];
            Cy1 = C1[1];
            }
            //Si son diferentes, usamos la funcion de puntos distintos
            else{//else3
            C1 = puntosDistintos(Cx1,x,Cy1,y,p);
            Cx1 = C1[0];
            Cy1 = C1[1];

            }//cierre else3

        }//cierre else2
    
    cuentaC1++;
    }//cierre while
    
    System.out.println("\nC1 resultante: ("+C1[0]+","+C1[1]+")\n");

    
    
///////////PARA CONSTRUIR C2////////////       

    int Cx2=0, Cy2=0;
    int C2[] = new int[2];    
    
//contados de sumas realizadas        
    int cuentaC2=1;

//tendremos que hacer n sumas
    while(cuentaC2<k){
    
//en el primer caso siempre los puntos serán iguales        
        if(cuentaC2==1){
        C2 = puntosIguales(Q[0],Q[1],a,p);
        Cx2 = C2[0];
        Cy2 = C2[1];        
        }
        //desde el segundo caso hay que comprobar si son iguales
        else{ //else2

            //Si son iguales, usamos la funcion de puntos iguales        
            if(Cx2==Q[0] && Cy2==Q[1]){
            C2 = puntosIguales(Cx2,Cy2,a,p);
            Cx2 = C2[0];
            Cy2 = C2[1];
            }
            //Si son diferentes, usamos la funcion de puntos distintos
            else{//else3
            C2 = puntosDistintos(Cx2,Q[0],Cy2,Q[1],p);
            Cx2 = C2[0];
            Cy2 = C2[1];

            }//cierre else3

        }//cierre else2
    
    cuentaC2++;
    }//cierre while    
    
    System.out.println("\nC2 parcial: ("+C2[0]+","+C2[1]+")");    

    C2 = puntosDistintos(Pm[0],Cx2,Pm[1],Cy2,p);
    System.out.println("\nC2 resultante: ("+C2[0]+","+C2[1]+")"); 
 
    
    int Cifrado[][] = new int[2][2];
    Cifrado[0]=C1;
    Cifrado[1]=C2;
    System.out.println("\nCIFRADO: { ("+Cifrado[0][0]+","+Cifrado[0][1]+"),("+Cifrado[1][0]+","+Cifrado[1][1]+") }"); 
    
    return Cifrado;
    
}

public int[] descifraECC(int x, int y, int a, int p, int[] C2, int privada){

//Usaremos la siguiente nomenclatura en los comentarios
//Pm = C2 - privada*(k*G)


///////////Resolviendo K*G////////////    

    int kgx=0, kgy=0;
    int KG[] = new int[2];
    
//contados de sumas realizadas        
    int cuentaKG=1;

//tendremos que hacer n sumas
    while(cuentaKG<privada){
    
//en el primer caso siempre los puntos serán iguales        
        if(cuentaKG==1){
        KG = puntosIguales(x,y,a,p);
        kgx = KG[0];
        kgy = KG[1];        
        }
        //desde el segundo caso hay que comprobar si son iguales
        else{ //else2

            //Si son iguales, usamos la funcion de puntos iguales        
            if(kgx==x && kgy==y){
            KG = puntosIguales(kgx,kgy,a,p);
            kgx = KG[0];
            kgy = KG[1];
            }
            //Si son diferentes, usamos la funcion de puntos distintos
            else{//else3
            KG = puntosDistintos(kgx,x,kgy,y,p);
            kgx = KG[0];
            kgy = KG[1];

            }//cierre else3

        }//cierre else2
    
    cuentaKG++;
    }//cierre while
    
    System.out.println("\nKG resultante: ("+KG[0]+","+KG[1]+")\n");


///////////Resolviendo privada*(K*G)//////////// 

    
    kgx=0; kgy=0;
    int PKG[] = new int[2];
    
//contados de sumas realizadas        
    cuentaKG=1;

//tendremos que hacer n sumas
    while(cuentaKG<privada){
    
//en el primer caso siempre los puntos serán iguales        
        if(cuentaKG==1){
        PKG = puntosIguales(KG[0],KG[1],a,p);
        kgx = PKG[0];
        kgy = PKG[1];        
        }
        //desde el segundo caso hay que comprobar si son iguales
        else{ //else2

            //Si son iguales, usamos la funcion de puntos iguales        
            if(kgx==x && kgy==y){
            PKG = puntosIguales(kgx,kgy,a,p);
            kgx = PKG[0];
            kgy = PKG[1];
            }
            //Si son diferentes, usamos la funcion de puntos distintos
            else{//else3
            PKG = puntosDistintos(kgx,KG[0],kgy,KG[1],p);
            kgx = PKG[0];
            kgy = PKG[1];

            }//cierre else3

        }//cierre else2
    
    cuentaKG++;
    }//cierre while

    System.out.println("\nPKG resultante: ("+PKG[0]+","+PKG[1]+")\n");
    
    
///////////Construyendo inverso aditivo de PKG////////////
    PKG[1] = PKG[1]*(-1);
    System.out.println("\nPKG inverso: ("+PKG[0]+","+PKG[1]+")\n");
    
///////////Resolviendo C2 + inverso( privada*(k*G) )////////////
 
        int descifrado[] = new int[2];
    
        if(C2[0]==PKG[0] && C2[1]==PKG[1]){
        descifrado = puntosIguales(PKG[0],PKG[1],a,p);
        }
        //Si son diferentes, usamos la funcion de puntos distintos
        else{//else0
        descifrado = puntosDistintos(PKG[0],C2[0],PKG[1],C2[1],p);
        }//cierre else0
        
        System.out.println("\n descifrado: ("+descifrado[0]+","+descifrado[1]+")\n");

        return descifrado;
        
}

public boolean validaAB(int a, int b){

    //Si nos da cero, los puntos no sirve
    if( ( (4*a*a*a)+(27*b*b) % 23 ) == 0){
    return false;
    }
    //Si es distinto de cero, se debe validar
    else{
    return true;
    }
}

public boolean validaPrivada(int cantPuntos, int privada){
if(privada<0 || privada>(cantPuntos-1))
    return false;
else
    return true;
}


public boolean validaGenerador(int x, int y, int a, int p){

    int Q[] = new int[2];
    int cuenta=1;

//tendremos que hacer n sumas
    while(true){
    
//en el primer caso siempre los puntos serán iguales        
        if(cuenta==1){
        Q = puntosIguales(x,y,a,p);
        }
        //desde el segundo caso hay que comprobar si son iguales
        else{ //else2

            //Si son iguales, usamos la funcion de puntos iguales        
            if(Q[0]==x && Q[1]==y){
            Q = puntosIguales(Q[0],Q[1],a,p);
            }
            
            //Si son diferentes, usamos la funcion de puntos distintos
            else{//else3
            Q = puntosDistintos(Q[0],x,Q[1],y,p);
            //Encontrado el punto la pregunta es: ¿cuenta+1 es primo?
            //Si lo es, el punto es válido
            if(Q[0]==0 && Q[1]==0 && esPrimo(cuenta+1)==true){System.out.println("El punto generador es válido en n:"+(cuenta+1));return true;}
            //Si no lo es, el punto es inválido
            if(Q[0]==0 && Q[1]==0 && esPrimo(cuenta+1)==false){System.out.println("El punto generador es inválido en n:"+(cuenta+1));return false;}
            }//cierre else3

        }//cierre else2
    
    cuenta++;
    }//cierre while
}


public boolean esPrimo(int num){

    boolean bandera = false;
    int divisor=2;
    
    while(!bandera && divisor<num){
        if (num%divisor==0)
        return false;
        else
        divisor++;
    }

    return true;
    
}




}
